import time
from datetime import datetime, timedelta

from flask import render_template, g, session, request, current_app, redirect, url_for, jsonify

from info import db
from info.constants import USER_COLLECTION_MAX_NEWS, QINIU_DOMIN_PREFIX
from info.models import User, News, Category
from info.modules.admin import admin_blu
from info.utils.common import user_login_data
from info.utils.img_storage import upload_img
from info.utils.response_code import RET, error_map

"""后台登录"""
#用admin蓝图装饰视图函数
@admin_blu.route('/login',methods=["POST","GET"])
def login():
    #思路
    #判断请求方式，如果是get,就渲染模板
    #方式为get时，应该先判断是否已登录
    #如果已登录并且有超级管理员的属性，可以直接进入后台首页
    #如果已经登录却没有管理员的属性，和第一次登录的管理员
    #给他渲染后台登录页面

    #如果请求方式是post，那么就是在登录了，
    #获取参数，账号和密码
    #校验参数是否完整
    #查询是否有该超级管理员
    #校验密码是否正确
    #状态保持，不光要保存user_id,还要存一个超级管理员的标记
    #重定向到后台首页


    # 判断请求方式，如果是get,就渲染模板
    if request.method == "GET":
        # 方式为get时，应该先判断是否已登录
        user_id = session.get("user_id")
        is_admin = session.get("is_admin")
        if user_id and is_admin:
            user = User.query.get(user_id)
            # 如果已登录并且有超级管理员的属性，可以直接进入后台首页
            return render_template("admin/index.html",user=user.to_dict())
        # 如果已经登录却没有管理员的属性，和第一次登录的管理员
        # 给他渲染后台登录页面
        return render_template("admin/login.html")

    # 如果请求方式是post，那么就是在登录了，前端用的是form表单
    # 获取参数，账号和密码
    username = request.form.get("username")
    password = request.form.get("password")
    # 校验参数是否完整
    if not all([username,password]):
        return render_template("admin/login.html",errmsg="用户名、密码不全")
    # 查询是否有该超级管理员
    try:
        user = User.query.filter(User.mobile == username, User.is_admin == True).first()
    except BaseException as e:
        current_app.logger.error(e)
        return render_template("admin/login.html",errmsg="数据库查询错误")

    if not user:
        return render_template("admin/login.html",errmsg="用户不存在")
    # 校验密码是否正确
    if not user.check_password(password):
        return render_template("admin/login.html",errmsg="用户名/密码错误")
    # 状态保持，不光要保存user_id,还要存一个超级管理员的标记
    session["user_id"] = user.id
    session["is_admin"] = True
    # 重定向到后台首页
    return redirect(url_for("admin.index"))

"""后台首页"""
@admin_blu.route('/index')
@user_login_data
def index():
    #后台首页里有iframe外链，所以后天首页只要渲染，将用户模型对象传进去即可
    return render_template("admin/index.html",user=g.user)


"""后台登出"""
@admin_blu.route('/logout')
def logout():
    #思路
    #只要将session里面的删除即可
    #然后重定向到新闻首页

    # 只要将session里面的删除即可
    #当超级管理员在正常的新闻页面登录的时候，也是可以登出的，所以在新闻页登出的时候，也要将超级管理员标记删除
    session.pop("user_id",None)
    session.pop("is_admin",None)
    # 然后重定向到新闻首页
    return redirect("/")


"""后台显示用户数量"""
@admin_blu.route('/user_count')
def user_count():

    #用户总数
    total_count = 0
    try:
        #不是超级管理员的用户总数
        total_count = User.query.filter(User.is_admin == False).count()
    except BaseException as e:
        current_app.logger.error(e)

    #月新增
    mon_count = 0
    #现在的时间
    t = time.localtime()
    #新生成 当月1号的日期字符串，格式化年月，自定义为1日
    date_mon_str = "%d-%02d-01" % (t.tm_year,t.tm_mon)
    #日期字符串转日期对象
    date_mon = datetime.strptime(date_mon_str,"%Y-%m-%d")
    try:
        #查询不是管理员的用户并且注册日期大于本月一日的用户数量
        mon_count = User.query.filter(User.is_admin == False,User.create_time > date_mon).count()
    except BaseException as e:
        current_app.logger.error(e)

    #日新增
    day_count = 0
    #新生成 当月一号的日期字符串
    t = time.localtime()
    date_day_str = "%d-%02d-%02d" % (t.tm_year,t.tm_mon,t.tm_mday)
    #日期字符串转日期对象,默认为某年某月某日的零点零分零秒
    date_day = datetime.strptime(date_day_str,"%Y-%m-%d")
    try:
        day_count = User.query.filter(User.is_admin == False,User.create_time >= date_day).count()
    except BaseException as e:
        current_app.logger.error(e)

    today_str = "%d-%02d-%02d" % (t.tm_year,t.tm_mon,t.tm_mday)
    active_count = []
    active_time = []
    try:
        for i in range(0,31):
            begin_date = datetime.strptime(date_day_str,"%Y-%m-%d") - timedelta(days=i)
            end_date = datetime.strptime(date_day_str,"%Y-%m-%d") + timedelta(days=1) - timedelta(days=i)
            #上面的那句可以简写成
            # end_date = datetime.strptime(date_day_str,"%Y-%m-%d") + timedelta(days=1 - i)

            #查询一天的登录量
            one_day_count = User.query.filter(User.is_admin == False,User.last_login >= begin_date,User.last_login < end_date).count()
            active_count.append(one_day_count)
            active_time.append(begin_date.strftime("%Y-%m-%d"))
    except BaseException as e:
        current_app.logger.error(e)

    active_time.reverse()
    active_count.reverse()
    data = {
        "total_count":total_count,
        "mon_count":mon_count,
        "day_count":day_count,
        "active_count":active_count,
        "active_time":active_time
    }

    return render_template("admin/user_count.html",data=data)


"""显示用户列表"""
@admin_blu.route('/user_list')
def user_list():
    #就是显示列表，和收藏的新闻列表一样
    #获取参数
    page = request.args.get("p",1)
    try:
        page = int(page)
    except BaseException as e:
        current_app.logger.error(e)
        page = 1

    #查询所有的用户
    user_list = []
    total_pages = 1
    try:
        pn = User.query.filter(User.is_admin == False).paginate(page,USER_COLLECTION_MAX_NEWS)
        user_list = [user.to_admin_dict() for user in pn.items]
        total_pages = pn.pages

    except BaseException as e:
        current_app.logger.error(e)

    data = {
        "total_page":total_pages,
        "user_list":user_list,
        "cur_page":page
    }

    return render_template("admin/user_list.html",data=data)


"""显示待审核新闻列表"""
@admin_blu.route('/news_review')
def news_review():
    #思路
    #显示的都是新闻的状态为0的所有新闻，
    #并且以分页的形式
    #并且有搜索功能，所以要让前端将关键字传给后端进行查询
    #用的是get请求方式，先获取参数，当前的页数和关键字
    #校验参数及格式转换
    #查询所有用户发布的新闻
    #将总页数和所有新闻列表和当前页数封装成字典传入模板进行渲染


    # 用的是get请求方式，先获取参数，当前的页数和关键字
    page = request.args.get("p", 1)
    keywords = request.args.get("keywords")
    # 校验参数及格式转换
    try:
        page = int(page)
    except BaseException as e:
        current_app.logger.error(e)
        #把当前页设置为1
        page = 1
    # 查询所有用户发布的新闻
    news_list = []
    total_pages = 1
    #列表的放的是数据库的查询条件
    filter_list = [News.user_id != None]
    if keywords:
        #如果有关键字传来，那么就把标题里包含关键字的条件添加到列表里面，作为数据库查询条件
        filter_list.append(News.title.contains(keywords))
    try:
        pn = News.query.filter(*filter_list).paginate(page,USER_COLLECTION_MAX_NEWS)
        news_list = [news.to_review_dict() for news in pn.items]
        total_pages = pn.pages
    except BaseException as e:
        current_app.logger.error(e)
    total_pages = pn.pages
    # 将总页数和所有新闻列表和当前页数封装成字典传入模板进行渲染
    data = {
        "total_page":total_pages,
        "news_list":news_list,
        "cur_page":page
    }

    return render_template("admin/news_review.html",data=data)


"""显示待审核新闻详情（按审核按钮后跳转的页面）"""
@admin_blu.route('/news_review_detail/<int:news_id>')
def news_review_detail(news_id):
    #思路
    #显示页面，只需要将新闻模型对象传入即可
    #新闻id是拼接在url后面的，通过新闻id查询到新闻模型对象
    #将模型对象传入模板进行渲染

    #查询该新闻数据
    try:
        news = News.query.get(news_id)
    except BaseException as e:
        current_app.logger.error(e)
        return render_template("admin/news_review_detail.html",data={"errmsg":"数据库查询错误"})

    #将新闻模型对象传入模板
    return render_template("admin/news_review_detail.html",data={"news":news.to_dict()})


"""提交审核（看过用户发布的新闻点击审核是否通过）"""
@admin_blu.route('/news_review_action',methods=['POST'])
def news_review_action():
    #思路
    #是否通过审核需要获取的参数是是否通过审核的动作和审核的是哪条新闻id，还要有审核未通过的原因，原因可以在需要用的时候再取
    #校验参数及格式转换
    #通过新闻id查询新闻模型对象，判断该新闻是否存在
    #根据审核动作处理，如果审核通过，那么将新闻状态改为0
    #如果未通过审核那么将状态改为-1，并将原因获取到再存进数据库
    # 返回json结果给前端

    # 是否通过审核需要获取的参数是是否通过审核的动作和审核的是哪条新闻id，还要有审核未通过的原因，原因可以在需要用的时候再取
    action = request.json.get("action")
    news_id = request.json.get("news_id")
    # 校验参数及格式转换
    if not all([action,news_id]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    if action not in ["accept","reject"]:
        jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    try:
        news_id = int(news_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 通过新闻id查询新闻模型对象，判断该新闻是否存在
    try:
        news = News.query.get(news_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
    if not news:
        return jsonify(errno=RET.NODATA, errmsg=error_map[RET.NODATA])

    # 根据审核动作处理，如果审核通过，那么将新闻状态改为0
    if action == "accept":
        news.status = 0
    # 如果未通过审核那么将状态改为-1，并将原因获取到再存进数据库
    else:
        news.status = -1
        reason = request.json.get("reason")
        if not reason:
            return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
        news.reason = reason
    # 返回json结果给前端
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])


"""显示版式编辑列表"""
@admin_blu.route('/news_edit')
def news_edit():
    #思路
    #就是显示审核通过的所有新闻，然后有个编辑按钮，可以纠正一些版式和分类
    #和显示待审核新闻列表一页，也有关键字搜索功能
    #获取参数时也是当前页数，关键字
    #校验参数及格式转换
    #查询所有审核通过的新闻
    #将总页数和当前页数和审核通过的所有新闻列表封装成字典传进模板中

    # 获取参数时也是当前页数，关键字
    page = request.args.get("p", 1)
    keywords = request.args.get("keywords")
    # 校验参数及格式转换

    try:
        #如果没有传page,那么也可以捕捉到参数异常错误，也可以赋值为默认的1
        page = int(page)
    except BaseException as e:
        current_app.logger.error(e)
        page = 1
    # 查询所有审核通过的新闻,还有一些爬虫爬到的新闻没有状态的属性，也应该查询出来可以编辑
    news_list = []
    total_pages = 1
    filter_list = [News.status != -1,News.status !=1]
    if keywords:
        filter_list.append(News.title.contains(keywords))
    try:
        pn = News.query.filter(*filter_list).paginate(page,USER_COLLECTION_MAX_NEWS)
        news_list = [news.to_review_dict() for news in pn.items]
        total_pages = pn.pages
    except BaseException as e:
        current_app.logger.error(e)
    # 将总页数和当前页数和审核通过的所有新闻列表封装成字典传进模板中
    data = {
        "total_page":total_pages,
        "news_list":news_list,
        "cur_page":page
    }

    return render_template("admin/news_edit.html",data=data)


"""显示及提交版式编辑详情页面"""
@admin_blu.route('/news_edit_detail',methods=['GET','POST'])
def news_edit_detail():
    #当显示编辑详情页面时用的是get请求，提交时用的是post请求
    #判断请求方式为get时，先获取参数，编辑的是哪个新闻id
        #校验参数及格式转换
        #根据新闻id查询出新闻模型对象
        #判断该新闻是否存在
        #因为可以编辑分类，所以需要查询出所有的分类，
        #再查询出该新闻的分类，添加标记
        #记得要将分类id为1的最新分类排除
        #将新闻模型对象和所有分类列表传入模型进行渲染
    #请求方式为post时，要获取可以编辑的所有属性
        #校验参数是否完整及格式转换
        #查询新闻和分类模型对象
        #判断新闻和分类对象是否存在
        #将修改的新闻模型属性进行赋值自动提交保存
        #判断是否修改了索引图片（版式编辑时是否上传了新图片），
        # form表单是获取不到用户新闻的索引图片，只能获取到用form表单新上传的图片
        # 返回json结果给前端


    if request.method == "GET":
    # 判断请求方式为get时，先获取参数，编辑的是哪个新闻id
        news_id = request.args.get("news_id")
        # 校验参数及格式转换
        try:
            news_id = int(news_id)
        except BaseException as e:
            current_app.logger.error(e)
            return render_template("admin/news_edit_detail.html",data={"errmsg":"参数有误"})
        # 根据新闻id查询出新闻模型对象
        try:
            news = News.query.get(news_id)
        except BaseException as e:
            current_app.logger.error(e)
            return render_template("admin/news_edit_detail.html",data={"errmsg":"数据库查询失败"})
        # 判断该新闻是否存在
        if not news:
            return render_template("admin/news_edit_detail.html",data={"errmsg":"指定的新闻未找到"})
        # 因为可以编辑分类，所以需要查询出所有的分类，
        category_list = []
        try:
            categories = Category.query.all()
            # 再查询出该新闻的分类，添加标记
            for category in categories:
                #将分类模型对象转换成字典
                category_dict = category.to_dict()
                is_selected = False
                #如果新闻的分类等于遍历出的某个分类，就把标记改为true
                if news.category_id == category.id:
                    is_selected = True
                category_dict["is_selected"] = is_selected
                category_list.append(category_dict)
        except BaseException as e:
            current_app.logger.error(e)
            return render_template("admin/news_edit_detail.html",data={"errmsg":"数据查询失败"})
        # 记得要将分类id为1的最新分类排除
        if len(category_list):
            category_list.pop(0)
        # 将新闻模型对象和所有分类列表传入模型进行渲染
        return render_template("admin/news_edit_detail.html",data={"news":news.to_dict(),"categories":category_list})
    # 请求方式为post时，要获取可以编辑的所有属性
    news_id = request.form.get("news_id")
    title = request.form.get("title")
    category_id = request.form.get("category_id")
    digest = request.form.get("digest")
    index_image = request.files.get("index_image")
    content = request.form.get("content")

    # 校验参数是否完整及格式转换
    if not all([news_id,title,category_id,digest,content]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    try:
        news_id = int(news_id)
        category_id = int(category_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 查询新闻和分类模型对象
    try:
        news = News.query.get(news_id)
        category = Category.query.get(category_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
    # 判断新闻和分类对象是否存在
    if not news or not category:
        return jsonify(errno=RET.NODATA, errmsg=error_map[RET.NODATA])
    # 将修改的新闻模型属性进行赋值自动提交保存
    news.title = title
    news.category_id = category_id
    news.digest = digest
    news.content = content
    # 判断是否修改了索引图片（版式编辑时是否上传了新图片），
    # form表单是获取不到用户新闻的索引图片，只能获取到用form表单新上传的图片
    if index_image:
        try:
            #读取出新图片内容，然后传到七牛云空间，得到访问图片的url的key,再加上前缀
            img_bytes = index_image.read()
            key = upload_img(img_bytes)
            news.index_image_url = QINIU_DOMIN_PREFIX + key
        except BaseException as e:
            current_app.logger.error(e)
    # 返回json结果给前端
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])


"""显示新闻有几种分类列表"""
@admin_blu.route('/news_type',methods=['GET','POST'])
def news_type():
    #因为新闻有分类，后台可以更改某个具体分类的名称，也可以增加一个新分类
    #判断请求方式，如果是get请求，显示渲染页面即可
    #查询出所有分类，把分类模型对象传入模板进行渲染
    #如果是post请求，那么不是修改分类就是新增分类
    #先获取参数，分类的名称和分类的id，如果是新增分类，那么有新分类的名称就可以了，
    #如果是修改分类名称，那么除了新改的分类名称，还需要得到原来的分类id，这样就能沿用原来的分类id
    #判断是否传了分类id,没传的话新增的分类名称进行校验及格式转换
    # 如果传了分类id,进行格式转换，
    #根据分类id将分类名称修改
    # 如果没传分类id,那么就新建分类模型对象，增加名称属性，并添加至主动提交数据库


    # 判断请求方式，如果是get请求，显示渲染页面即可
    if request.method == 'GET':
        # 查询出所有分类，把分类模型对象传入模板进行渲染
        categories = []
        try:
            categories = Category.query.all()
        except BaseException as e:
            current_app.logger.error(e)
        #记得要将最新的分类排除
        if len(categories):
            categories.pop(0)
        return render_template("admin/news_type.html",categories=categories)
    # 如果是post请求，那么不是修改分类就是新增分类
    # 先获取参数，分类的名称和分类的id，如果是新增分类，那么有新分类的名称就可以了，
    # 如果是修改分类名称，那么除了新改的分类名称，还需要得到原来的分类id，这样就能沿用原来的分类id
    name = request.json.get("name")
    id = request.json.get("id")
    # 判断是否传了分类id,没传的话新增的分类名称进行校验及格式转换
    if not name:
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 如果传了分类id,修改分类,进行格式转换，
    if id:
        try:
            id = int(id)
            category = Category.query.get(id)
        except BaseException as e:
            current_app.logger.error(e)
            #也可能是数据库查询错误，这里写的不严谨
            return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
        # 根据分类id将分类名称修改
        category.name = name
    else:
        # 如果没传分类id,那么就新建分类模型对象，增加名称属性，并添加至主动提交数据库
        new_category = Category()
        new_category.name = name
        db.session.add(new_category)

    #返回结果给前端
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])

